// import com.alibaba.druid.sql.ast.SQLExpr;
// import com.alibaba.druid.sql.ast.SQLObject;
// import com.alibaba.druid.sql.ast.SQLSetQuantifier;
// import com.alibaba.druid.sql.ast.statement.*;
// import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock;
// import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerTop;
// import com.alibaba.druid.sql.parser.*;

#include "SQLServerSelectParser.h"
#include "../../../ast/SQLExpr.h"
#include "../../../ast/SQLObject.h"
#include "../../../ast/SQLSetQuantifier.h"
#include "../ast/SQLServerSelectQueryBlock.h"
#include "../ast/SQLServerTop.h"
#include "SQLServerExprParser.h"
#include "../../../ast/statement/SQLExprHint.h"

SQLServerSelectParser::SQLServerSelectParser(string_ptr sql)
    : SQLSelectParser(SQLServerExprParser_ptr(new SQLServerExprParser(sql)))
{
}

SQLServerSelectParser::SQLServerSelectParser(SQLExprParser_ptr exprParser)
    : SQLSelectParser(exprParser)
{
}

SQLServerSelectParser::SQLServerSelectParser(SQLExprParser_ptr exprParser, SQLSelectListCache_ptr selectListCache)
    : SQLSelectParser(exprParser, selectListCache)
{
}

SQLSelect_ptr SQLServerSelectParser::select()
{
  SQLSelect_ptr select = SQLSelect_ptr(new SQLSelect());

  if (lexer->token()->name->c_str() == Token::WITH.name->c_str())
  {
    SQLWithSubqueryClause_ptr with = this->parseWith();
    select->setWithSubQuery(with);
  }

  select->setQuery(SQLSelectParser::query());
  select->setOrderBy(parseOrderBy());

  if (select->getOrderBy() == nullptr)
  {
    select->setOrderBy(parseOrderBy());
  }

  if (lexer->token()->name->c_str() == Token::FOR.name->c_str())
  {
    lexer->nextToken();

    if (lexer->identifierEquals(make_string_ptr("BROWSE")))
    {
      lexer->nextToken();
      select->setForBrowse(BOOL::TRUE);
    }
    else if (lexer->identifierEquals(make_string_ptr("XML")))
    {
      lexer->nextToken();

      for (;;)
      {
        if (lexer->identifierEquals(make_string_ptr("AUTO"))         //
            || lexer->identifierEquals(make_string_ptr("TYPE"))      //
            || lexer->identifierEquals(make_string_ptr("XMLSCHEMA")) //
        )
        {
          select->getForXmlOptions()->push_back(make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal())));
          lexer->nextToken();
        }
        else if (lexer->identifierEquals(make_string_ptr("ELEMENTS")))
        {
          lexer->nextToken();
          if (lexer->identifierEquals(make_string_ptr("XSINIL")))
          {
            lexer->nextToken();
            select->getForXmlOptions()->push_back(make_string_ptr("ELEMENTS XSINIL"));
          }
          else
          {
            select->getForXmlOptions()->push_back(make_string_ptr("ELEMENTS"));
          }
        }
        else if (lexer->identifierEquals(make_string_ptr("PATH")))
        {
          SQLExpr_ptr xmlPath = this->exprParser->expr();
          select->setXmlPath(xmlPath);
        }
        else
        {
          break;
        }

        if (lexer->token()->name->c_str() == Token::COMMA.name->c_str())
        {
          lexer->nextToken();
          continue;
        }
        else
        {
          break;
        }
      }
    }
    else
    {
      std::string tmp = "syntax error, not support option : ";
      tmp += lexer->token()->name->c_str();
      tmp += ", ";
      tmp += encode_util::UnicodeToUTF8(lexer->info());
      throw new ParserException(make_string_ptr(tmp));
    }
  }

  if (lexer->identifierEquals(make_string_ptr("OFFSET")))
  {
    lexer->nextToken();
    SQLExpr_ptr offset = this->expr();

    acceptIdentifier("ROWS");
    select->setOffset(offset);

    if (lexer->token()->name->c_str() == Token::FETCH.name->c_str())
    {
      lexer->nextToken();
      acceptIdentifier("NEXT");

      SQLExpr_ptr rowCount = expr();
      acceptIdentifier("ROWS");
      acceptIdentifier("ONLY");
      select->setRowCount(rowCount);
    }
  }

  return select;
}

SQLSelectQuery_ptr SQLServerSelectParser::query(SQLObject_ptr parent, BOOL_ptr acceptUnion)
{
  if (lexer->token()->name->c_str() == Token::LPAREN.name->c_str())
  {
    lexer->nextToken();

    SQLSelectQuery_ptr select = SQLSelectParser::query();
    accept(Token::RPAREN);

    return queryRest(select, acceptUnion);
  }

  SQLServerSelectQueryBlock_ptr queryBlock = SQLServerSelectQueryBlock_ptr(new SQLServerSelectQueryBlock());

  if (lexer->token()->name->c_str() == Token::SELECT.name->c_str())
  {
    lexer->nextToken();

    if (lexer->token()->name->c_str() == Token::COMMENT.name->c_str())
    {
      lexer->nextToken();
    }

    if (lexer->token()->name->c_str() == Token::DISTINCT.name->c_str())
    {
      queryBlock->setDistionOption(SQLSetQuantifier::DISTINCT);
      lexer->nextToken();
    }
    else if (lexer->token()->name->c_str() == Token::ALL.name->c_str())
    {
      queryBlock->setDistionOption(SQLSetQuantifier::ALL);
      lexer->nextToken();
    }

    if (lexer->token()->name->c_str() == Token::TOP.name->c_str())
    {
      SQLServerTop_ptr top = this->createExprParser()->parseTop();
      queryBlock->setTop(top);
    }

    parseSelectList(queryBlock);
  }

  if (lexer->token()->name->c_str() == Token::INTO.name->c_str())
  {
    lexer->nextToken();

    SQLTableSource_ptr into = this->parseTableSource();
    queryBlock->setInto(std::dynamic_pointer_cast<SQLExprTableSource>(into));
  }

  parseFrom(queryBlock);

  parseWhere(queryBlock);

  parseGroupBy(queryBlock);

  queryBlock->setOrderBy(this->exprParser->parseOrderBy());

  parseFetchClause(queryBlock);

  return queryRest(queryBlock, acceptUnion);
}

SQLServerExprParser_ptr SQLServerSelectParser::createExprParser()
{
  return SQLServerExprParser_ptr(new SQLServerExprParser(lexer));
}

SQLTableSource_ptr SQLServerSelectParser::parseTableSourceRest(SQLTableSource_ptr tableSource)
{
  if (lexer->token()->name->c_str() == Token::WITH.name->c_str())
  {
    lexer->nextToken();
    accept(Token::LPAREN);

    for (;;)
    {
      SQLExpr_ptr expr = this->expr();
      SQLExprHint_ptr hint = SQLExprHint_ptr(new SQLExprHint(expr));
      hint->setParent(tableSource);
      tableSource->getHints()->push_back(hint);
      if (lexer->token()->name->c_str() == Token::COMMA.name->c_str())
      {
        lexer->nextToken();
        continue;
      }
      else
      {
        break;
      }
    }

    accept(Token::RPAREN);
  }

  return SQLSelectParser::parseTableSourceRest(tableSource);
}
